import binascii
import os 
# following imports are required by PKI
import Crypto
import Crypto.Random
from Crypto.PublicKey import RSA 
from Crypto.Signature import PKCS1_v1_5 #用于签名
from Crypto.Hash import SHA #用于签名
import hashlib #用于hash计算
import configparser #处理ini文件
import time
import datetime
import base64

iniFileName = "Account.ini" #用于存储账户余额
txtFileName = 'TranscationLists.txt' #用于存储交易信息
TransactionList = [] #用来存储所有的交易信息
blockSavedPath = r'C:\myblcokchain'


def findfiles(filesPath):#查找指定文件夹下的区块链文件
    #查找文件代码
    if not os.path.exists(filesPath):
        os.makedirs(filesPath)
    files = os.listdir(filesPath)
    filesList = []
    for s in files:
        sPath = os.path.join(filesPath, s)
        if os.path.isdir(sPath):
            findfiles(sPath, filesList)
        elif os.path.isfile(sPath) and 'block' in s:
            filesList.append(sPath)
    return filesList

def GetLastBlockInfo(): #找到最后一个区块的ID和Hash值
    fList = findfiles(blockSavedPath)
    fCount = len(fList)
    if fCount == 0:
        #生产0号/创世区块
        blockId = 0
        previousBlockHash = 0
        blockTranStr = '-'
        timestamp = int(time.time())        
        miner = '-'
        blockStr1 = "blockId:%s,previousBlockHash:%s,blockTranStr:%s,miner:%s,timestamp:%s" % (blockId,previousBlockHash,blockTranStr,miner,timestamp)        
        blockHash = Crypto.Hash.SHA256.new()
        blockHash.update(blockStr1.encode('utf-8'))
        blockHashStr = blockHash.hexdigest()
        blockStr2 = "%s,blockHash:%s\n\n\n" % (blockStr1,blockHashStr) 
        newBlockFile = r"%s\block%s.txt" % (blockSavedPath,blockId)
        f = open(newBlockFile, "a+")
        f.write(blockStr2)
        f.close()
    else:
        blockId = fCount-1
        blockFile = r"%s\block%s.txt" % (blockSavedPath,blockId)
        blockHashStr = ''
        f = open(blockFile, "r")
        strs = f.read()
        f.close()
        strs = strs.split('\n\n\n')
        for str in strs:
            list1 = str.split(',')
            for lStr in list1:
                if lStr.split(":")[0] == 'blockHash':
                    blockHashStr = lStr.split(":")[1]

    return blockId,blockHashStr



def GetAccountCredit(address=''):
    f = open(txtFileName, "r")
    tranStr = f.read()
    f.close()
    strs = tranStr.split('\n\n')
    rstValue = 0
    for str in strs:
        if len(str) > 0:
            list1 = str.split(',')
            sender = list1[0].split(":")[1]
            recipient = list1[1].split(":")[1]
            valueStr = list1[2].split(":")[1]
            timestampStr = list1[3].split(":")[1]
            signStr = list1[4].split(":")[1]
            pubkey = list1[5].split(":")[1]
            if address == recipient:
                rstValue += float(valueStr)
    print("账户:%s 的余额为:%.8f" % (address,rstValue))
    return rstValue

class Student:
    Name = ''
    address = ''
    Credits = 20.0
    private_key = '' #修改为可以外部访问
    public_key = ''  #修改为可以外部访问
    def __init__(self,stuName):
        PrivateFile = "%s_Private.pem" % (stuName)
        if os.path.exists(PrivateFile):
            #导入已有账户的私钥
            with open(PrivateFile, "rb") as f:
                privatePemStr = f.read()
                self.private_key = Crypto.PublicKey.RSA.importKey(privatePemStr)
        else:
            #创建新的账户
            random = Crypto.Random.new().read
            self.private_key = RSA.generate(1024, random)
            # 保存密钥
            with open(PrivateFile, "wb") as f:
                privatePemStr = self.private_key.exportKey()   # 导出私钥
                f.write(privatePemStr)
                f.close()

        publickFile = "%s_Public.pem" % (stuName)
        if os.path.exists(publickFile):
            #导入已有账户的公钥
            with open(publickFile, "rb") as f:
                publicPemStr = f.read()
                self.public_key = Crypto.PublicKey.RSA.importKey(publicPemStr)
        else:
            self.public_key = self.private_key.publickey()
            publicPemStr = self.public_key.exportKey()   # 导出公钥
            with open(publickFile, "wb") as f:
                f.write(publicPemStr)
        #计算出账户
        str1 = self.public_key.exportKey(format='PEM').decode('utf-8')
        h1 = Crypto.Hash.SHA256.new(str1.encode('utf-8'))
        str2 = binascii.hexlify(h1.digest()).decode('utf-8')
        h2 = Crypto.Hash.SHA256.new(str2.encode('utf-8'))
        str3 = binascii.hexlify(h2.digest()).decode('utf-8')
        self.address = str3.upper()
        print("Account is ready: %s" % (self.address))

class Miner(Student): #继承
    #矿工，负责验证交易，并把验证通过的交易记录到账本中
    def VerifyTransaction(self,TransactionList):
        #验证TransactionList中的交易记录，如果正确则保存到交易记录账本中
        rstValue = False
        blockTranStr = '' #block的交易字符串
        for str in TransactionList:
            if len(str) > 0:
                rstValue = False
                list1 = str.split(',')
                sender = list1[0].split(":")[1]
                recipient = list1[1].split(":")[1]
                valueStr = list1[2].split(":")[1]
                timestampStr = list1[3].split(":")[1]
                signStr = list1[4].split(":")[1]
                pubkey = list1[5].split(":")[1]
                tranDetailStr = "sender:%s,recipient:%s,value:%s,timestamp:%s" % (sender,recipient,valueStr,timestampStr)
                print(str)

                pubkeyPem = base64.b32decode(pubkey)
                keyPub = Crypto.PublicKey.RSA.importKey(pubkeyPem)
                verifer = Crypto.Signature.PKCS1_v1_5.new(keyPub)
                msg_hash = Crypto.Hash.SHA256.new()
                msg_hash.update(tranDetailStr.encode('utf-8'))
                hStr1 = msg_hash.hexdigest()
                rstValue = verifer.verify(msg_hash, base64.b32decode(signStr)) # 使用公钥验证签名

                if rstValue:
                    str1 = str + "\n"
                    blockTranStr += str1
        TransactionList = [] #clear transction list

        if len(blockTranStr) > 0:
            previousBlockId,previousBlockHash = GetLastBlockInfo()
            blockId = int(previousBlockId) + 1
            timestamp = int(time.time()) #时间戳
            miner = self.address
            blockStr1 = "blockId:%s,previousBlockHash:%s,blockTranStr:[%s],miner:%s,timestamp:%s" % (blockId,previousBlockHash,blockTranStr,miner,timestamp)        
            blockHash = Crypto.Hash.SHA256.new()
            blockHash.update(blockStr1.encode('utf-8'))
            blockHashStr = blockHash.hexdigest()
            blockStr2 = "%s,blockHash:%s\n\n\n" % (blockStr1,blockHashStr) 
            newBlockFile = r"%s\block%s.txt" % (blockSavedPath,blockId)            
            f = open(newBlockFile, "w+")
            f.write(blockStr2)
            f.close()
        
        return 

    def TransactionSave(self, tranStr): #存储交易信息
        f = open(txtFileName, "a+")
        f.write(tranStr)
        f.close()
        return
   
class Transaction:
    def __init__(self,stu):
        self._private_key = stu.private_key
        self._private_key_str = base64.b32encode(self._private_key.exportKey(format='PEM'))
        self._public_key = stu.public_key
        self._public_key_str = base64.b32encode(self._public_key.exportKey(format='PEM'))
        return

    def Transfer(self, sender, recipient, value):
        self.timestamp = int(time.time())
        print('Transaction finished at %s' % (self.timestamp))

        tranDetailStr = "sender:%s,recipient:%s,value:%.8f,timestamp:%s" % (sender.address,recipient.address,value,self.timestamp)
        signStr = self.SignTransaction(tranDetailStr)
        str1 = "%s,signstr:%s,pubkey:%s" % (tranDetailStr,signStr,self._public_key_str.decode('utf-8'))
        
        TransactionList.append(str1)
        return 

    def SignTransaction(self,tranStr=''): #返回签名后的字符串, tranStr为源字符串、没有经过hash处理
        signer = PKCS1_v1_5.new(self._private_key)
        h = Crypto.Hash.SHA256.new(tranStr.encode('utf-8'))
        hStr = h.hexdigest()
        str2 = signer.sign(h)
        str3 = base64.b32encode(str2).decode('utf-8')
        return str3

StuA = Student('A')
StuB = Student('B')
tran = Transaction(StuA)    # StuA发起的交易
tran.Transfer(StuA,StuB,1.1)
tran.Transfer(StuA,StuB,1.2)
minerA = Miner('minerA')
minerA.VerifyTransaction(TransactionList)

GetAccountCredit(StuB.address)

